package com.breeze.leetcode;

import java.util.*;

/**
 * https://leetcode-cn.com/problems/lru-cache/
 * 146. LRU缓存机制
 * 运用你所掌握的数据结构，设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作： 获取数据 get 和 写入数据 put 。
 * <p>
 * 获取数据 get(key) - 如果关键字 (key) 存在于缓存中，则获取关键字的值（总是正数），否则返回 -1。
 * 写入数据 put(key, value) - 如果关键字已经存在，则变更其数据值；如果关键字不存在，则插入该组「关键字/值」。当缓存容量达到上限时，它应该在写入新数据之前删除最久未使用的数据值，从而为新的数据值留出空间。
 * <p>
 *  
 * <p>
 * 进阶:
 * <p>
 * 你是否可以在 O(1) 时间复杂度内完成这两种操作？
 */

/**
 * 方法一：  通过队列维护最久未使用的key值，队头为最久未使用的key
 * <p>
 * 时间复杂度:O(n) ,在队列中移除元素时，需要遍历链表查找到对应元素
 * 空间复杂度：O(n)
 */
public class LeetCode146 {
    private HashMap<Integer, Integer> maps = new HashMap<>();
    private Queue<Integer> lruQueue = new LinkedList<>();
    private final int capacity;

    public LeetCode146(int capacity) {
        this.capacity = capacity;
    }

    public int get(int key) {
        if (maps.containsKey(key)) {
            lruQueue.remove(key);
            lruQueue.offer(key);
            return maps.get(key);
        } else {
            return -1;
        }
    }

    public void put(int key, int value) {
        if (maps.containsKey(key)) {
            maps.put(key, value);
            lruQueue.remove(key);
        } else {
            if (maps.size() >= capacity) {
                Integer removeKey = lruQueue.poll();
                maps.remove(removeKey);
            }
            maps.put(key, value);
        }
        lruQueue.offer(key);
    }

    public void each() {
        for (Integer integer : lruQueue) {
            System.out.println(integer);
        }
    }

    /**
     * 方法二：Java api类 LinkedHashMap，内部通过HashMap+双向链表实现
     */
    static class Solution2 extends LinkedHashMap<Integer, Integer> {
        int capacity;

        public Solution2(int capacity) {
            super(capacity, 0.75F, true);
            this.capacity = capacity;
        }

        public int get(int key) {
            return super.getOrDefault(key, -1);
        }

        public void put(int key, int value) {
            super.put(key, value);
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<Integer, Integer> entry) {
            return size() > capacity;
        }
    }

    /**
     * 方法三：哈系表+双向链表
     * 时间复杂度：O(1)
     * 空间复杂度：O(n)
     */
    static class Solution3 {
        int capacity;
        // 值存存储的是Node，可以保留Node的关系，方便以O(1)复杂度增删，否则需要遍历链表才可以找到对应节点
        HashMap<Integer, Node> cache = new HashMap<>();
        // 双向链表的空的头和尾，对于最就未使用的始终存储在链表的真正的尾部
        // 小贴士
        // 在双向链表的实现中，使用一个伪头部（dummy head）和伪尾部（dummy tail）标记界限，这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。
        Node head = new Node();
        Node tail = new Node();

        public Solution3(int capacity) {
            this.capacity = capacity;
            head.next = tail;
            tail.prev = head;
        }

        public int get(int key) {
            Node node = cache.get(key);
            if (node == null) {
                return -1;
            }
            moveToHead(node);
            return node.value;
        }

        public void put(int key, int value) {
            // 找到节点，有则更新，移动到头部
            Node node = cache.get(key);
            if (node != null) {
                node.value = value;
                moveToHead(node);
            } else {
                // 无则创建节点添加到头部，如果大于capacity，则移除尾部
                node = new Node(key, value);
                if (cache.size() >= capacity) {
                    int deleteKey = removeTail().key;
                    cache.remove(deleteKey);
                }
                cache.put(key, node);
                addToHead(node);
            }
        }


        private void moveToHead(Node node) {
            removeNode(node);
            addToHead(node);
        }

        private void addToHead(Node node) {
            node.next = head.next;
            node.prev = head;
            head.next.prev = node;
            head.next = node;
        }

        private void removeNode(Node node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }

        private Node removeTail() {
            Node res = tail.prev;
            removeNode(res);
            return res;
        }

        static class Node {
            int key;
            int value;
            Node prev;
            Node next;

            public Node() {
            }

            public Node(int key, int value) {
                this.key = key;
                this.value = value;
            }
        }
    }

    public static void main(String[] args) {
        Solution3 leetCode146 = new Solution3(5);
        leetCode146.put(2, 20);
        leetCode146.put(1, 10);
        leetCode146.put(3, 30);
        System.out.println(leetCode146.get(2));
        leetCode146.put(5, 50);
        leetCode146.put(4, 40);
        leetCode146.put(6, 60); // 溢出，应移除key为1的元素
        System.out.println(leetCode146.get(1));
        // leetCode146.each();
    }
}
